// Copyright (c) 2022 Presto Labs Pte. Ltd.
// Author: hyuan

#pragma once

#include <string>
#include <utility>
#include <vector>

#include <boost/algorithm/string.hpp>
#include <boost/tokenizer.hpp>
#include <boost/foreach.hpp>

#include "coin2/exchange/base/api_base/asio_http_context.h"
#include "coin2/exchange/base/api_base/base_auth.h"
#include "coin2/exchange/base/api_util/api_signature.h"
#include "coin2/exchange/base/api_util/auth_key.h"
#include "coin2/exchange/base/api_util/url_params_builder.h"

class MexcBaseAuth {
 public:
  using HeaderSet = std::vector<std::pair<std::string, std::string>>;

  MexcBaseAuth() {
    header_set_.push_back(std::make_pair("ApiKey", ""));
    header_set_.push_back(std::make_pair("Request-Time", ""));
    header_set_.push_back(std::make_pair("Signature", ""));
    header_set_.push_back(std::make_pair("Content-Type", "application/json"));

    header_set_v3_.push_back(std::make_pair("X-MEXC-APIKEY", ""));
    header_set_v3_.push_back(std::make_pair("Content-Type", "application/json"));
  }

  ~MexcBaseAuth() = default;
  MexcBaseAuth(const MexcBaseAuth&) = delete;
  MexcBaseAuth& operator=(const MexcBaseAuth&) = delete;

  auto GetHeaderSet(const std::string& query_str) -> const HeaderSet&;
  auto GetHeaderSetV3() -> const HeaderSet&;
  inline void SetAccessKey(const std::string& access_key) {
    access_key_ = access_key;
    CHECK_THROW(!access_key_.empty()) << "[mexc] access key is empty";
  }
  inline void SetSecretKey(const std::string& secret_key) {
    secret_key_ = secret_key;
    CHECK_THROW(!secret_key_.empty()) << "[mexc] secret key is empty";
  }

  inline std::string GetAccessKey() { return access_key_; }
  inline std::string GetSecretKey() { return secret_key_; }

  std::string GetQueryString(const std::string& query_str) {
    std::string res = "";
    boost::char_separator<char> token_sep("&");
    boost::tokenizer params(query_str, token_sep);
    std::map<std::string, std::string> m;
    BOOST_FOREACH(const std::string& param, params)
    {
      std::vector<std::string> v;
      boost::split(v, param, boost::is_any_of("="));
      if (v.size() != 2) {
        continue;
      }
      if (v[0].empty() || v[1].empty()) {
        continue;
      }
      m.insert(std::make_pair(v[0], v[1]));
    }
    for (auto x: m)
    {
      res += x.first + "=" + ApiSignatureUtil::UrlEncode(x.second) + "&";
    }
    if (!res.empty()) {
      res = res.substr(0, res.length() - 1);
    }
    return res;
  }

  static std::string GetSignature(const std::string& secret_key, const std::string& param) {
    if (secret_key.empty()) {
      throw std::runtime_error("[Mexc] API key and secret key are required");
    }
    return ApiSignatureUtil::GetSignatureByHexHmacSHA256(param, secret_key);
  }

 private:
  std::string access_key_;
  std::string secret_key_;
  HeaderSet header_set_;
  HeaderSet header_set_v3_;
};

class MexcWsAuth : public MexcBaseAuth {
 public:
  explicit MexcWsAuth(const std::string& key_filepath)
      : key_(AuthKey::FromFile(key_filepath)) {
    SetAccessKey(key_.access_key());
    SetSecretKey(key_.secret_key());
  }

  const std::string CreateSignature(
      const std::string& op,
      const std::string& timestamp) const;

 protected:
  AuthKey key_;
};

class MexcRestAuth : public MexcBaseAuth, public BaseRestAuth {
 public:
  explicit MexcRestAuth(
      const std::string& key_filepath,
      const std::string& host,
      const std::string& port,
      const std::string& bind_ip)
      : key_(AuthKey::FromFile(key_filepath)), host_(host), port_(port), bind_ip_(bind_ip) {
    SetAccessKey(key_.access_key());
    SetSecretKey(key_.secret_key());
  }
  explicit MexcRestAuth(
      const std::string& key_filepath,
      const std::string& host,
      int32_t port,
      const std::string& bind_ip)
      : key_(AuthKey::FromFile(key_filepath)),
        host_(host),
        port_(std::to_string(port)),
        bind_ip_(bind_ip) {
    SetAccessKey(key_.access_key());
    SetSecretKey(key_.secret_key());
  }

  AsioHttpContext* CreateRequestByGetWithSignature(
      const std::string& address,
      UrlParamsBuilder* builder) override;
  AsioHttpContext* CreateRequestByPostWithSignature(
      const std::string& address,
      UrlParamsBuilder* builder) override;
  AsioHttpContext* CreateRequestByDeleteWithSignature(
      const std::string& address,
      UrlParamsBuilder* builder) override;
  AsioHttpContext* CreateRequestByPutWithSignature(
      const std::string& address,
      UrlParamsBuilder* builder) override {
    return nullptr;
  }

  private:
  static std::string BuildSignaturePath(const std::string& secret_key, const std::string& param) {
    std::string code = ApiSignatureUtil::EscapeURL(GetSignature(secret_key, param));
    return "signature=" + code;
  }

 protected:
  AuthKey key_;
  std::string host_;
  std::string port_;
  std::string bind_ip_;
};
